from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import Sequence
from direct.showbase.DirectObject import DirectObject
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import getModelPath
from pandac.PandaModules import *
from player import Player
from retrymenu import RetryMenu
from win import WinMenu
from acorn import Acorn
import direct.directbase.DirectStart
import math, os, random, sys

class World(DirectObject):
    def __init__(self):
        self.win = False
        
        # Control map
        self.init = False
        self.keyMap = {"left":0, "right":0, "brake":0}
        self.accept("escape"   , sys.exit)
        self.accept("mouse1"   , self.setKey, ["brake", 1])
        self.accept("mouse1-up", self.setKey, ["brake", 0])
        self.control_brakeAmmount = 0
        
        self.inst1 = OnscreenText(text="mouse1: Air brake"    , style=1, fg=(1,1,1,1), pos=(-1.3, 0.95), align=TextNode.ALeft, scale = .05)
        self.inst2 = OnscreenText(text="Press mouse1 to begin", style=1, fg=(1,1,1,1), pos=(-1.3, 0.90), align=TextNode.ALeft, scale = .05)
        
        # Initialize properties
        self.initProperties() 
        self.initGravity()
        self.initLights()

        # Collision
        base.collHandler = PhysicsCollisionHandler()
        base.collHandler.addInPattern('%fn-into-%in')
        #base.collHandler.setHorizontal(False)
        base.collHandler.setDynamicFrictionCoef(0.6) 
        base.collHandler.setStaticFrictionCoef(0.6)
        base.cTrav = CollisionTraverser()
        base.cTrav.setRespectPrevTransform(True)

        # Load components
        self.player = Player()
        self.acorn = Acorn()
        self.initCamera()
        self.initSkybox()
        self.initGround()
        self.initTrees()
        self.initSound()
        
        self.player.nodepath.setPos(0, 0, 1800)
        self.acorn.nodepath.setPos(-10, 15, 1750)

        # Add collision callbacks
        self.accept('PlayerCollision-into-AcornCollision' , self.PlayerIntoAcorn)
        self.accept('PlayerCollision-into-GroundCollision', self.PlayerIntoGround)
        self.accept('AcornCollision-into-GroundCollision' , self.AcornIntoGround)       
            
        # Add tasks
        taskMgr.add(self.updateCamera, "TaskUpdateCamera")
        taskMgr.add(self.updateMove  , "TaskUpdateMove")
    ############################################################################
    def PlayerIntoGround(self, entry):
        if not self.win:
            self.player.dead = True
            self.scream.play()
            taskMgr.add(self.waitShowRetry, "TaskWaitShowRetry")
    ############################################################################
    def PlayerIntoAcorn(self, entry):
        self.win = False
        text = TextNode('WinText')
        text.setText("Got it!")
        textNodePath = aspect2d.attachNewNode(text)
        textNodePath.setScale(0.1)
        textNodePath.setPos(0.0, 0.0, 0.9)
        self.acorn.nodepath.reparentTo(self.player.nodepath)
        taskMgr.add(self.waitShowWin, "TaskWaitShowWin")
    ############################################################################
    def AcornIntoGround(self, entry):
        pass
        #print "Aim for the nut!"
    ############################################################################
    def setKey(self, key, value):
        if (self.init == False) and (key == "brake") and (value == 1):
            self.inst2.hide()
            self.init = True
            self.player.Start()
            self.acorn.Start()
            
        self.keyMap[key] = value
    ############################################################################   
    def waitShowRetry(self, task):
        if task.time < 3.0:
            return task.cont
        self.bg.stop()
        self.wind_fx.stop()
        RetryMenu()
        return task.done

    def waitShowWin(self, task):
        if task.time < 2.0:
            return task.cont
        self.bg.stop()
        self.wind_fx.stop()
        WinMenu()
        return task.done
    
    def updateCamera(self, task):
        if base.mouseWatcherNode.hasMouse():
            md = base.win.getPointer(0)
            x = md.getX() 
            y = md.getY() 
            deltaX = md.getX() - 200 
            deltaY = md.getY() - 200 
            base.win.movePointer(0, 200, 200) 
            self.player.nodepath.setH(self.player.nodepath.getH() - 0.3 * deltaX) 
            self.cameraPitch = self.cameraPitch + 0.3 * deltaY 
            if (self.cameraPitch < -60): self.cameraPitch = -60 
            if (self.cameraPitch > 80): self.cameraPitch = 80 
            base.camera.setHpr(0, self.cameraPitch, 0) 
            base.camera.setPos(0, 0, self.cameraTargetHeight / 2) 
            base.camera.setY(base.camera, self.cameraDistance)

        viewTarget = Point3(0, 0, self.cameraTargetHeight) 
        base.camera.lookAt(viewTarget) 
        
        return task.cont
    ############################################################################
    def updateMove(self, task):
        # Never move if dead
        if self.player.dead:
            return task.cont
        
        # Calculate the player vertical inclination parameter
        brakeMin = 0
        brakeMax = 100
        brakeDelta = brakeMax * globalClock.getDt()
        
        if (self.keyMap["brake"] == 0): # Not braking
            self.control_brakeAmmount = max(self.control_brakeAmmount - brakeDelta, brakeMin)
        else: # Braking
            self.control_brakeAmmount = min(self.control_brakeAmmount + brakeDelta, brakeMax)
        
        self.player.BrakeAmmount(self.control_brakeAmmount)
        
        return task.cont
    ############################################################################
    def initSound(self):
        self.bg = base.loader.loadSfx("sounds/bg.mp3")
        self.bg.setVolume(0.25)
        
        self.wind_fx = base.loader.loadSfx("sounds/wind.mp3")
        self.wind_fx.setLoop(True)
        #self.wind_fx.setVolume(0.5)
        
        self.scream = base.loader.loadSfx("sounds/scream.ogg")
        
        self.bg.play()
        self.wind_fx.play()
    
    def initProperties(self):
        props = WindowProperties()
        props.setCursorHidden(True)
        base.win.requestProperties(props)
        base.win.setClearColor(Vec4(0, 0, 0, 1))
        base.enableParticles()
    ############################################################################
    def initGravity(self):
        # Prepare gravity force
        gravityExageration = 10
        gravityNode = ForceNode('WorldForces')
        gravityNodePath = render.attachNewNode(gravityNode)
        gravityForce = LinearVectorForce(0, 0, -9.81 * gravityExageration)
        gravityNode.addForce(gravityForce)
        
        # Add it to the physics manager -> Applies to all objects with physics enabled
        base.physicsMgr.addLinearForce(gravityForce)
    ############################################################################
    def initLights(self):
        render.setShaderAuto() 
        render.setAttrib(LightRampAttrib.makeHdr1()) 
        ambientLight = AmbientLight("LightAmbient") 
        ambientLight.setColor(Vec4(.8, .8, .8, 1)) 
        directionalLight = DirectionalLight("LightDirectional") 
        directionalLight.setDirection(Vec3(10, -10, -5)) 
        directionalLight.setColor(Vec4(2.0, 2.0, 2.0, 1.0)) 
        directionalLight.setSpecularColor(Vec4(2.0, 2.0, 2.0, 1)) 
        render.setLight(render.attachNewNode(ambientLight)) 
        render.setLight(render.attachNewNode(directionalLight))
    ############################################################################    
    def initCamera(self):
        base.camera.reparentTo(self.player.nodepath)
        base.camLens.setFov(100)
        self.cameraDistance = 18
        self.cameraTargetHeight = 5.0 
        self.cameraPitch = 3142 
        base.disableMouse() 
        props = WindowProperties()
        props.setCursorHidden(True) 
        base.win.requestProperties(props) 
    ############################################################################
    def initSkybox(self):
        self.skybox = loader.loadModel("models/sky")
        self.skybox.setScale(2000, 2000, 4000)
        self.skybox.setPos(0, 0, 0)
        self.skybox.setTwoSided(True)
        self.skybox.setDepthWrite(True)
        self.skybox.reparentTo(render)
        #self.skybox.reparentTo(self.player.actor)
    ############################################################################
    def initTrees(self):
        trunkTexture = loader.loadTexture("models/acorn.png")
        trunkTexture.setWrapU(Texture.WMRepeat)
        trunkTexture.setWrapV(Texture.WMRepeat)
        self.cylinderModel = loader.loadModel("models/cylinder.dae")
        self.cylinderModel.setTexture(trunkTexture)

        # Big trunks
        for trunkIndex in range(1, 5):
            trunk = render.attachNewNode("Trunk" + str(trunkIndex))
            trunk.setScale(400, 400, 4000)
            trunk.setPos(600 * pow(-1, trunkIndex % 2), 600 * pow(-1, trunkIndex % 3), 0)
            #trunk.setColor(trunkIndex%2 * 32, trunkIndex%3 * 32, trunkIndex%4 * 32) # Debugging
            self.cylinderModel.instanceTo(trunk)
            # Collision
            trunkCollision = render.attachNewNode(CollisionNode("TrunkCollision" + str(trunkIndex)))
            trunk.copyTo(trunkCollision)
            trunkCollision.setCollideMask(BitMask32.allOn())
            trunkCollision.flattenLight()
            
            # Branches
            for branchIndex in range(1, 18):
                branch = render.attachNewNode("Trunk" + str(trunkIndex) + "Branch" + str(branchIndex))
                #branch.setColor(trunkIndex%2 * 32, trunkIndex%3 * 32, trunkIndex%4 * 32) # Debugging
                branch.setScale(15, 15, 900)
                branch.setPos(600 * pow(-1, trunkIndex % 2), 600 * pow(-1, trunkIndex % 3), 100 * branchIndex)
                
                angle = random.random()*60 - 30
                if trunkIndex == 1: angle += 135
                if trunkIndex == 2: angle += 135
                if trunkIndex == 3: angle += 45
                if trunkIndex == 4: angle += 45 
                branch.setHpr(0, 90, angle)
                self.cylinderModel.instanceTo(branch)
                
                # Collision
                branchCollision = render.attachNewNode(CollisionNode("Trunk" + str(trunkIndex) + 
                                                                     "Branch" + str(branchIndex) + "Collision"))
                branch.copyTo(branchCollision)
                branchCollision.setCollideMask(BitMask32.allOn())
                branchCollision.flattenLight()
        
        
    ############################################################################
    def initGround(self):
        geoMipImage = PNMImage()
        geoMipImage.read(getModelPath().findFile("models/terrain.pnm"))
        grassTexture = loader.loadTexture("models/grass.jpg")
        self.ground = GeoMipTerrain("Ground")        
        self.ground.setHeightfield(geoMipImage)
        self.ground.setAutoFlatten(GeoMipTerrain.AFMOff)
        self.ground.setBruteforce(True)
        self.ground.setBlockSize(32) 
        self.ground.setMinLevel(1) 
        self.ground.setNear(10)
        self.ground.setFar(10)
        self.ground.generate()
        self.ground.getRoot().setTexture(grassTexture)
        self.ground.getRoot().setPos(-2000, -2000, -500)
        self.ground.getRoot().setScale(8, 8, 1)
        self.ground.getRoot().setSz(60)
        self.ground.getRoot().setTwoSided(True)
        self.ground.getRoot().setDepthWrite(True)
        self.ground.getRoot().reparentTo(render)
        
        # Ground Collision
        self.GroundCollision = render.attachNewNode(CollisionNode("GroundCollision"))
        self.GroundCollision.node().addSolid(CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, -500))))
        self.GroundCollision.show()
        
        # Load ground collision mesh -> Problems detecting collision
        #self.ground.getRoot().copyTo(self.GroundCollision)
        #self.GroundCollision.hide()
        #self.GroundCollision.setCollideMask(BitMask32.allOn())
        #self.GroundCollision.flattenLight()
    ############################################################################
